Skip to content

fix: butterfly fleeing stuck state + HUD stat legend#89

Merged
biosynthart merged 6 commits into
hellolifeforms:mainfrom
biosynthart:fix/godot-client-sync
Jun 21, 2026
Merged

fix: butterfly fleeing stuck state + HUD stat legend#89
biosynthart merged 6 commits into
hellolifeforms:mainfrom
biosynthart:fix/godot-client-sync

Conversation

@biosynthart

Copy link
Copy Markdown
Member

Summary

Fixes multiple bugs where butterflies got permanently trapped in FLEEING state, and adds a stat legend to the selection HUD.

Bug: Butterflies stuck in FLEEING → slowly die of exhaustion

Root cause (server)

The engine runs phases in this order: Flow → Movement → Interactions → Guards. Two bugs combined to create a trap:

  1. FleeActor re-fired every tick — as long as a predator was in sensory range, it emitted a new SetTarget, resetting the escape position. The guard actor only exits FLEEING when _target is None, so _target was never cleared.

  2. MovementActor filled the gap — when the butterfly reached its escape target and _target was cleared, MovementActor (running in Flow phase) immediately set a new wander target. The guard (running after Flow) then saw _target was set and never exited FLEEING.

For butterflies with floral_affinity, sensory range = grid_max (entire meadow), so any bird anywhere kept the flee alive.

FLEEING is in ACTIVE_ENERGY_DRAIN_STATES, so energy bled away until the butterfly died.

Root cause (client)

  • Logic inversion in _evaluate_fleeing: best == null or flee_targets.has() was wrong — entered flee block when no threat was found
  • No target caching: if the bird wasn't found this frame (position divergence), returned Vector2.ZERO → butterfly frozen in place

Fixes

File Change
server/.../interaction_actors.py FleeActor only fires on state entry (early return if already FLEEING)
server/.../movement_actors.py MovementActor skips FLEEING entities so guard gets a clean tick to exit
client/.../agency.gd Fixed null-check logic + added flee direction caching (3s timeout)
client/.../world_model.gd Added flee_dir, flee_dir_expiry, FLEE_DIR_TIMEOUT fields

HUD: Per-entity-type stat legend

Added a legend in the top-right corner that auto-shows when selecting an entity, explaining each stat icon:

  • Animal / Bird: 🍖 Hunger, ⚡ Energy, 💧 Hydration, ❤️ Health, 💕 Repro drive, ⏳ Age
  • Plant / Tree: 💧 Hydration, 🌱 Growth, 🧪 Nutrients, ❤️ Health, ⏳ Age
  • Insect: 🍖 Hunger, ⚡ Energy, 🐝 Colony health, 💕 Repro drive
  • Microorganism: 🧫 Population, 🔬 Activity

All values shown as (0–100) except age which shows (ticks).

Files changed

  • client/godot/scenes/main.gd — legend label, \_build_legend(), visibility toggling
  • client/godot/scenes/main.tscn — LegendLabel node
  • client/godot/scripts/agency.gd — flee logic fix + direction caching
  • client/godot/scripts/autoloads/world_model.gd — flee persistence fields
  • server/ecosim/actors/interaction_actors.py — FleeActor entry-only guard
  • server/ecosim/actors/movement_actors.py — MovementActor FLEEING skip

Testing

All 39 unit tests pass (actor tests + movement actor tests).

- LMB click (tap, not drag) selects nearest entity via 3D raycast
  that accounts for flight altitude (birds, insects)
- Selected entity pulses with bright highlight color in-world
- Center-top HUD label shows species, state, and drive stats
- Esc or click on empty ground deselects
- Auto-deselect when selected entity dies

Changes:
- scenes/main.gd: click detection, raycast-to-point selection,
  HUD stats panel with per-type drive formatting
- scenes/main.tscn: SelectionLabel node on HUD, updated help text
- scripts/renderer.gd: highlight color pulse for selected entity
Server-side fixes:
- FleeActor only fires on state entry (not every tick), preventing
  continuous target resets that trapped entities in FLEEING forever
- MovementActor skips FLEEING entities so the guard actor can cleanly
  transition FLEEING -> IDLE when escape target is reached. Without
  this, MovementActor would set a wander target before the guard ran,
  keeping _target non-None and blocking the exit transition

Client-side fixes (Godot):
- Fixed inverted null-check in _evaluate_fleeing: 'best == null or
  flee_targets.has()' was wrong; corrected to 'best != null and
  flee_targets.has()'
- Added flee direction caching (flee_dir + flee_dir_expiry) so
  entities keep running in the last known safe direction when the
  threat is momentarily not found (client/server position divergence)

HUD improvement:
- Added per-entity-type stat legend (LegendLabel) in top-right corner
- Explains each icon: hunger, energy, hydration, health, growth,
  colony health, etc. with value ranges (0-100 or ticks)
- Auto-shows/hides when selecting/deselecting entities
Both clients had the same issue as the Godot client: when the server
says FLEEING but the client can't find a threat (position divergence,
stale data), they fell through to wander mid-flee instead of keeping
the escape direction.

Added flee direction caching (fleeDirX/Y + fleeDirExpiry, 3s timeout)
to both clients, matching the pattern already applied to Godot.
Multiple .py files had malformed module docstrings where the opening
triple-quote was missing, causing Python to parse the indented
docstring body as code (IndentationError/SyntaxError).

Converted all affected headers to plain # comments, consistent with
the rest of the codebase and matching the Godot/browser clients.
Godot 4 expects grow_horizontal/grow_vertical values 0-3 (Begin/Center/End/Both).
The editor-created LegendLabel had value 4, causing two startup errors.
Valid values are Begin (0), Center (1), End (2). Changed to 1 (Center).
@biosynthart biosynthart merged commit 407e515 into hellolifeforms:main Jun 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant